package  {
  import away3d.containers.ObjectContainer3D;
  import away3d.core.base.SubGeometry;
  import away3d.materials.MaterialBase;
  import away3d.primitives.Cube;
	import away3d.primitives.PrimitiveBase;
  import flash.display.Bitmap;
  import flash.display.BitmapData;
  import flash.geom.Rectangle;
  import flash.geom.Vector3D;
  import mge.Timer;
	
	/**
   * ...
   * @author yo
   * 
   * // TODO Añadir "faldones".
   */
  public class HeightMap extends PrimitiveBase {
    
    
    public var gridN:int = 64;
    public var gridSpacing:Number = 1500 / gridN;
    private var heightMapData:BitmapData;
    public var maxHeight:Number = 1000;
    private var segment:Rectangle;
    
    public function HeightMap(heightMap:BitmapData, segment:Rectangle, xpos:Number, zpos:Number, material : MaterialBase = null) {
      super(material);
      this.heightMapData = heightMap;
      this.castsShadows = false;
      
      this.segment = segment;
      this.x = xpos;
      this.z = zpos;
    }
    
    protected override function buildGeometry(target : SubGeometry) : void
    {
      var t:Timer = new Timer(10000);
      t.start();
      
      var vertexData:Vector.<Number> = new Vector.<Number>();
      var normalData:Vector.<Number> = new Vector.<Number>();
      var indexData:Vector.<uint> = new Vector.<uint>();
      var uvs:Vector.<Number> = new Vector.<Number>();    
      
      for (var i:int = 0; i < gridN+1; i++)
      for (var j:int = 0; j < gridN+1; j++)
      {
          // builds vertices
          var xx:Number = i * gridSpacing;
          var zz:Number = j * gridSpacing;
          var yy:Number = heightAtPosition(xx, zz);
          
          vertexData.push(xx);
          vertexData.push(yy);
          vertexData.push(zz);
          
          // builds normals (from image)
          if ( (i == 0 && segment.x == 0) || (j == 0 && segment.y == 0) || 
               (i == gridN - 1 && segment.x + segment.width >= heightMapData.width) || 
               (j == gridN - 1 && segment.y + segment.height >= heightMapData.height) )
          {
            // Border normal
            normalData.push(0);
            normalData.push(1);
            normalData.push(0);
          }
          else
          {
            var v1:Vector3D = new Vector3D(0, heightAtPosition(xx, zz + gridSpacing) - heightAtPosition(xx, zz - gridSpacing), gridSpacing);
            var v2:Vector3D = new Vector3D(gridSpacing, heightAtPosition(xx + gridSpacing, zz) - heightAtPosition(xx - gridSpacing, zz), 0);
            var vn:Vector3D = v1.crossProduct(v2);
            vn.normalize();
            
            normalData.push(vn.x);
            normalData.push(vn.y);
            normalData.push(vn.z);
          }
                    
          // Constructs uvs
          uvs.push(i / (gridN));
          uvs.push(j / (gridN));
      }      
                  
      // Constructs indexes
      for (i = 0; i < gridN; i++)
      for (j = 0; j < gridN; j++)
      {
          var base:uint = i * (gridN+1) + j;
          
          indexData.push(base);
          indexData.push(base + gridN + 2);
          indexData.push(base + gridN + 1);
          
          indexData.push(base);
          indexData.push(base + 1);
          indexData.push(base + gridN + 2);
      }
      
      // target.useFaceWeights = true;      
      // the engine can compute normal/tangents, but mine looks better.
      //target.autoDeriveVertexNormals = true;
      //target.autoDeriveVertexTangents = true;
      
      // Updates heightmap data.
      target.updateVertexData(vertexData);
      target.updateVertexNormalData(normalData);
      target.updateIndexData(indexData);
      target.updateUVData(uvs);
      
      var secondsElapsed:Number = t.getDelta() * 10;
      if (secondsElapsed > 1)
        trace("building heightfield geometry done (in " + secondsElapsed + "s.)");
    }
    
    // Given x and z world coordinates, returns approximated height at that point.
    public function heightAtPosition(x:Number, z:Number):Number
    {
        /*
      // Nearest neighbour interpolation
      var relx:Number = x / width;
      var rely:Number = z / width;
      var px:int = relx * heightMapData.width + heightMapData.width / 2;
      var py:int = rely * heightMapData.height + heightMapData.height / 2;
      var pixel:uint = heightMapData.getPixel32(px, py) >> 16 & 0xFF;
      return pixel / 255 * maxHeight;
      return 0;
      */
      /*
      if (x >= 375)
      {
        trace("limit");
      }*/
      
      var p1xNum:Number = segment.x + x / width * segment.width;
      var p1zNum:Number = segment.y + z / width * segment.height;
      
      return heightAtMapPosition(p1xNum, p1zNum);
    }
    
    // Returns the height in a given point. x, y are texture pixels of the height map.
    public function heightAtMapPosition(x:int, y:int):Number
    {
        var pi:Number = bilinearFilter(x, y);      
        return pi / 255 * maxHeight;
    }
    
    // Devuelve el valor interpolado de una posicion en el mapa de altura.
    private function bilinearFilter(x:Number, z:Number):Number
    {
      var p1x:int = x;
      var p1z:int = z;
      
      /*
      if (p1x + 1 >= heightMapData.width || p1z + 1 >= heightMapData.height)
      {
        // En los limites devuelve valores coherentes.
        return (heightMapData.getPixel32(p1x, p1z) >> 16 & 0xFF);
      }*/
      
      var p1:int = heightMapData.getPixel32(p1x, p1z) >> 16 & 0xFF;
      var p2:int = heightMapData.getPixel32(p1x + 1, p1z) >> 16 & 0xFF;
      var p3:int = heightMapData.getPixel32(p1x, p1z + 1) >> 16 & 0xFF;
      var p4:int = heightMapData.getPixel32(p1x + 1, p1z + 1) >> 16 & 0xFF;
      
      var dx:Number = x - p1x;
      var dz:Number = z - p1z;
      
      var ti:Number = p1 + (p2 - p1) * dx;
      var bi:Number = p3 + (p4 - p3) * dx;
      var pi:Number = ti + (bi - ti) * dz;
      
      return pi;
    }
        
    public function get width() : Number
    {
        return gridN * gridSpacing;
    }
    
    override protected function buildUVs(target : SubGeometry) : void
    {
        // target.updateUVData(uvs);
    }
  }

}